﻿#pragma once
#include <cstdint>
#include <DirectXMath.h>
#include <vector>

class FGeometryGenerator
{
public:
    using uint16 = std::uint16_t;
    using uint32 = std::uint32_t;

    struct Vertex
    {
        Vertex() {}
        
        Vertex(
            const DirectX::XMFLOAT3& InPos,
            const DirectX::XMFLOAT3& InNormal,
            const DirectX::XMFLOAT3& InTangent,
            const DirectX::XMFLOAT2& InTexCoord
            )
            : Position(InPos)
            , Normal(InNormal)
            , Tangent(InTangent)
            , TexCoord(InTexCoord)
        {}

        Vertex(
            float InPosX, float InPosY, float InPosZ,
            float InNormalX, float InNormalY, float InNormalZ,
            float InTangentX,  float InTangentY, float InTangentZ,
            float InU, float InV
            )
            : Position(InPosX, InPosY, InPosZ)
            , Normal(InNormalX, InNormalY, InNormalZ)
            , Tangent(InTangentX, InTangentY, InTangentZ)
            , TexCoord(InU, InV)
        {}

        DirectX::XMFLOAT3 Position;
        DirectX::XMFLOAT3 Normal;
        DirectX::XMFLOAT3 Tangent;
        DirectX::XMFLOAT2 TexCoord;
    };

    struct MeshData
    {
        std::vector<Vertex> Vertices;
        std::vector<uint32> Indices32;

        std::vector<uint16>& GetIndices16()
        {
            if (mIndices16.empty())
            {
                mIndices16.resize(Indices32.size());
                for (size_t i = 0; i < Indices32.size(); i++)
                {
                    mIndices16[i] = static_cast<uint16>(Indices32[i]);
                }
            }

            return mIndices16;
        }

    private:
        std::vector<uint16> mIndices16;
    };

    ///<summary>
    /// Creates a box centered at the origin with the given dimensions, where each
    /// face has m rows and n columns of vertices.
    ///</summary>
    MeshData CreateBox(float width, float height, float depth, uint32 numSubdivisions);

    ///<summary>
    /// Creates a sphere centered at the origin with the given radius.  The
    /// slices and stacks parameters control the degree of tessellation.
    ///</summary>
    MeshData CreateSphere(float radius, uint32 sliceCount, uint32 stackCount);

    ///<summary>
    /// Creates a geosphere centered at the origin with the given radius.  The
    /// depth controls the level of tessellation.
    ///</summary>
    MeshData CreateGeosphere(float radius, uint32 numSubdivisions);

    ///<summary>
    /// Creates a cylinder parallel to the y-axis, and centered about the origin.  
    /// The bottom and top radius can vary to form various cone shapes rather than true
    // cylinders.  The slices and stacks parameters control the degree of tessellation.
    ///</summary>
    MeshData CreateCylinder(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount);

    ///<summary>
    /// Creates an mxn grid in the xz-plane with m rows and n columns, centered
    /// at the origin with the specified width and depth.
    ///</summary>
    MeshData CreateGrid(float width, float depth, uint32 m, uint32 n);

    ///<summary>
    /// Creates a quad aligned with the screen.  This is useful for postprocessing and screen effects.
    ///</summary>
    MeshData CreateQuad(float x, float y, float w, float h, float depth);

private:
    void Subdivide(MeshData& meshData);
    Vertex MidPoint(const Vertex& v0, const Vertex& v1);
    void BuildCylinderTopCap(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount, MeshData& meshData);
    void BuildCylinderBottomCap(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount, MeshData& meshData);
};
